from select import select
import socket
from collections.abc import Callable
from typing import Any
from time import time,sleep
import logging
import threading

TA_INVALID = -1
TA_READ = 0
TA_WRITE = 1
TA_EXCEPT = 2
TA_TIMER = 3

class ThreadArg:
    def __init__(self, ttype = TA_READ, callback:Callable = None, value = None, fd = None, **args):
        self._fd = fd
        self._ttype = ttype
        self._callback = callback
        self._value = value
        self._args = args

    @property
    def fd(self):
        return self._fd

    @property
    def type(self):
        return self._ttype
    @type.setter
    def type(self, value):
        self._ttype = value

    @property
    def callback(self):
        return self._callback

    @property
    def value(self):
        return self._value

    @property
    def args(self):
        return self._args

    @property
    def timeout(self):
        try:
            return int(self._args['timeout'])
        except:
            return 0

class Thread(object):
    def __init__(self):
        self._r_fd_list = []
        self._w_fd_list = []
        self._x_fd_list = []
        
        self._r_th_list = []
        self._w_th_list = []
        self._x_th_list = []
        self._time_th_list = []

        self._ready_th_list = []
        self.lock = threading.Lock()

    
    def thread_read_add(self, callback:Callable, value:Any, fd):
        with self.lock:
            try:
                self._r_fd_list.append(fd)
                tt = ThreadArg(TA_READ, callback, value, fd)
                self._r_th_list.append(tt)
                return tt
            except Exception as e:
                logging.debug(f'thread_read_add error {e}')
                return None

    def thread_write_add(self, callback:Callable, value:Any, fd):
        with self.lock:
            try:
                self._w_fd_list.append(fd)
                tt = ThreadArg(TA_WRITE, callback, value, fd)
                self._w_th_list.append(tt)
                return tt
            except Exception as e:
                logging.debug(f'thread_write_add error {e}')
                return None

    def thread_except_add(self, callback:Callable, value:Any, fd):
        with self.lock:
            try:
                self._x_fd_list.append(fd)
                tt = ThreadArg(TA_EXCEPT, callback, value, fd)
                self._x_th_list.append(tt)
                return tt
            except Exception as e:
                logging.debug(f'thread_except_add error {e}')
                return None

    def thread_timer_add(self, callback:Callable, value:Any, timeout:int):
        with self.lock:
            try:
                tt = ThreadArg(TA_TIMER, callback, value, timeout = int(time()) + timeout)
                self._time_th_list.append(tt)
                #这里要做排序
                self._time_th_list.sort(key=lambda x:x.timeout)
                return tt
            except Exception as e:
                logging.debug(f'thread_timer_add error {e}')
                return None
    
    def thread_del(self, thread:ThreadArg):
        with self.lock:
            try:
                if thread.type == TA_READ:
                    self._r_fd_list.remove(thread.fd)
                    self._r_th_list.remove(thread) 
                elif thread.type == TA_WRITE:
                    self._w_fd_list.remove(thread.fd)
                    self._w_th_list.remove(thread) 
                elif thread.type == TA_EXCEPT:
                    self._x_fd_list.remove(thread.fd)
                    self._x_th_list.remove(thread) 
                elif thread.type == TA_TIMER:
                    self._t_th_list.remove(thread) 
                else:
                    pass
                thread.type = TA_INVALID
            except:
                pass

    def thread_process_fd(self, fl, tl, fdl):
        for fi in fl:
            for ti in tl:
                if ti.fd == fi:
                    fdl.remove(fi)
                    tl.remove(ti)
                    ti.type = TA_INVALID
                    self._ready_th_list.append(ti)
                    break

    def fetch(self):
        #先看定时器
        now = int(time())
        for it in self._time_th_list:
            if it.timeout < now:
                self._time_th_list.remove(it)
                it.type = TA_INVALID
                return it
        #再看ready列表
        for it in self._ready_th_list:
            self._ready_th_list.remove(it)
            it.type = TA_INVALID
            return it
        #再select
        timeout = 0
        if len(self._time_th_list)  > 0:
            timeout = self._time_th_list[0].timeout - now
        if timeout <= 0:
            timeout = 1

        # logging.debug(f'self._r_fd_list is {self._r_fd_list}, self._w_fd_list  is {self._w_fd_list} self._x_fd_list is {self._x_fd_list} timeout is {timeout}')
        if len(self._r_fd_list) == 0 and len(self._w_fd_list) == 0 and len(self._x_fd_list) == 0:
            sleep(timeout)
            return None
        rl, wl, el = select(self._r_fd_list, self._w_fd_list, self._x_fd_list, float(timeout))
        self.thread_process_fd(rl, self._r_th_list, self._r_fd_list)
        self.thread_process_fd(wl, self._w_th_list, self._w_fd_list)
        self.thread_process_fd(el, self._x_th_list, self._x_fd_list)
        return None

    def run(self):
        while True:
            try:
                th = self.fetch()
                # logging.debug(f'run th is {th}')
                if th is not None:
                    th._callback(th.value, th.fd, th.args)
            except Exception as e:
                logging.debug(f'run err {e}')
                return
            except KeyboardInterrupt:
                return

thread = Thread()    

def thread_read_add(callback:Callable, value:Any, fd):
    return thread.thread_read_add(callback, value, fd)

def thread_write_add(callback:Callable, value:Any, fd):
    return thread.thread_write_add(callback, value, fd)

def thread_except_add(callback:Callable, value:Any, fd):
    return thread.thread_except_add(callback, value, fd)

def thread_timer_add(callback:Callable, value:Any, timeout:int):
    return thread.thread_timer_add(callback, value, timeout)

def thread_del(th:ThreadArg):
    return thread.thread_del(th)

def thread_run():
    return thread.run()